.. _Свойства:

Свойства
========

.. index::
    свойства
    property

Свойства позволяют использовать функции-члены как переменные-члены.
Мы знакомы с этим свойством по фрагментам. Свойство length фрагмента
возвращает количество элементов этого фрагмента:

.. code-block:: d

    int[] slice = [ 7, 8, 9 ];
    assert(slice.length == 3);

Глядя только на это использование, можно подумать, что .length реализована как переменная-член:

.. code-block:: d

    struct SliceImplementation
    {
        int length;
        // ...
    }

Однако другая функциональность этого свойства доказывает, что оно не может быть переменной-членом: Присвоение нового значения свойству .length фактически
изменяет длину фрагмента, иногда добавляя новые элементы в
базовый массив:

.. code-block:: d

    slice.length = 5; // The slice now has 5 elements
    assert(slice.length == 5)

*Примечание: свойство.length массивов фиксированной длины не может быть изменено.*
Присвоение .length выше включает в себя более сложные операции, чем
чем простое изменение значения: определение того, есть ли в массиве место для новой
длины, выделение дополнительной памяти, если нет, и перемещение существующих элементов на
и перемещение существующих элементов на новое место; и, наконец, инициализация каждого дополнительного элемента с помощью .init.
Очевидно, что присвоение .length работает как функция.
Свойства - это функции-члены, которые используются как переменные-члены.

Вызов функций без круглых скобок
--------------------------------

Как уже говорилось в предыдущей главе, когда нет аргумента для
функции можно вызывать без круглых скобок:

.. code-block:: d

    writeln();
    writeln;

Эта функция тесно связана со свойствами, поскольку свойства используются почти всегда без круглых скобок.


Функции свойств, возвращающие значения
--------------------------------------

В качестве простого примера рассмотрим структуру прямоугольника, состоящую из двух
членов:

.. code-block:: d

    struct Rectangle
    {
        double width;
        double height;
    }

Предположим, что потребуется третье свойство этого типа, которое должно предоставлять площадь прямоугольника:

.. code-block:: d

Один из способов выполнить это требование - определить третьего члена:

.. code-block:: d

    struct Rectangle
    {
        double width;
        double height;
        double area;
    }

Недостаток такой конструкции в том, что объект может легко стать непоследовательным: Хотя
прямоугольники всегда должны иметь инвариант "ширина * высота == площадь", это
эта последовательность может быть нарушена, если позволить членам свободно и
независимо.
В качестве крайнего примера, объекты могут даже начинать свою жизнь в противоречивых
состояниях:

.. code-block:: d

    // Inconsistent object: The area is not 10 * 20 == 200.
    auto garden = Rectangle(10, 20, 1111);


Лучшим способом было бы представить понятие площади как свойство. Вместо того чтобы
определения дополнительного члена, значение этого члена вычисляется с помощью
функцией с именем area, таким же, как и концепция, которую она представляет:

.. code-block:: d

    struct Rectangle
    {
        double width;
        double height;
        double area() const
        {
            return width * height;
        }
    }

*Примечание: Как вы помните из главы о параметрахconst ref и функциях-членахconst*
(:ref:`Параметры const ref и константные функции-члены<Параметры const ref и константные функции-члены>`), *спецификатор const в объявлении функции гарантирует.что объект не будет изменен этой функцией.*

Эта функция свойства позволяет использовать структуру так, как будто у нее есть третий член
переменная:

.. code-block:: d

    auto garden = Rectangle(10, 20);
    writeln("The area of the garden: ", garden.area);

Поскольку значение свойства area вычисляется путем умножения ширины и
высоты прямоугольника, то на этот раз оно всегда будет одинаковым:

.. code-block:: console

    The area of the garden: 200

Функции свойств, используемые при присваивании
----------------------------------------------

Подобно свойству длины срезов, свойства пользовательских типов могут
также могут быть использованы в операциях присваивания:

.. code-block:: d

    garden.area = 50;

Чтобы это присваивание действительно изменило площадь прямоугольника, два
члены struct должны быть соответствующим образом изменены. Чтобы обеспечить эту функциональность,
мы можем предположить, что прямоугольник является гибким, поэтому для сохранения инварианта
"ширина * высота == площадь", стороны прямоугольника могут быть изменены.
Функция, обеспечивающая такой синтаксис присваивания, также называется area.
Значение, которое используется в правой части присваивания, становится единственным
параметром этой функции.
Следующее дополнительное определение функции area() позволяет использовать это свойство в
операциях присваивания и эффективно изменять площадь объектов Rectangle:


.. code-block:: d

    import std.stdio;
    import std.math;

    struct Rectangle
    {
        double width;
        double height;
        double area() const
        {
            return width * height;
        }

        void area(double newArea)
        {
            auto scale = sqrt(newArea / area);
            width *= scale;
            height *= scale;
        }
    }

    void main()
    {
        auto garden = Rectangle(10, 20);
        writeln("The area of the garden: ", garden.area);
        garden.area = 50;
        writefln("New state: %s x %s = %s",
            garden.width, garden.height, garden.area);
    }

Новая функция использует функцию sqrt из модуля std.math
которая возвращает квадратный корень из указанного значения. Когда обе величины
ширина и высота прямоугольника масштабируются на квадратный корень из этого отношения,
то площадь будет равна искомому значению.
В результате присвоение площади четверти ее текущего значения приводит к уменьшению вдвое
обе стороны прямоугольника:

.. code-block:: console

    The area of the garden: 200
    New state: 5 x 10 = 50


Свойства не являются абсолютно необходимыми
-------------------------------------------

Выше мы видели, как Rectangle можно использовать так, как будто у него есть третий член
переменная. Однако вместо свойств можно использовать и обычные функции-члены:

.. code-block:: d

    import std.stdio;
    import std.math;

    struct Rectangle
    {
        double width;
        double height;
        double area() const
        {
            return width * height;
        }

        void setArea(double newArea)
        {
            auto scale = sqrt(newArea / area);
            width *= scale;
            height *= scale;
        }
    }

    void main()
    {
        auto garden = Rectangle(10, 20);
        writeln("The area of the garden: ", garden.area());
        garden.setArea(50);
        writefln("New state: %s x %s = %s",
            garden.width, garden.height, garden.area());
    }


Кроме того, как мы уже видели в главе :ref:`Перегрузка функций (Overloading)<глава_49_start>`, эти
две функции могут даже иметь одинаковые имена:

.. code-block:: d

    double area() const
    {
    // ...
    }

    void area(double newArea)
    {
    // ...
    }

Когда использовать
------------------

Выбор между обычными функциями-членами и свойствами может оказаться непростым.
Иногда обычные функции-члены кажутся более естественными, а иногда
свойства.
Однако, как мы видели в главе :ref:`Инкапсуляция и защита атрибутов<глава_60_start>`, важно ограничить прямой доступ к переменным-членам.
Разрешение пользовательскому коду свободно изменять переменные-члены всегда приводит к
проблемы с обслуживанием кода. По этой причине переменные-члены лучше
инкапсулировать либо с помощью обычных функций-членов, либо с помощью функций свойств.
Оставлять такие члены, как ширина и высота, открытыми для общего доступа допустимо
только для очень простых типов. Почти всегда лучше использовать функции свойств.

.. code-block:: d

    struct Rectangle
    {
    private:
        double width_;
        double height_;
    public:
        double area() const
        {
            return width * height;
        }

        void area(double newArea)
        {
            auto scale = sqrt(newArea / area);
            width_ *= scale;
            height_ *= scale;
        }

        double width() const
        {
            return width_;
        }

        double height() const
        {
            return height_;
        }
    }


укажите, как сделать члены приватными, чтобы к ним могли обращаться только
соответствующие функции свойств.
Также обратите внимание, что, чтобы не перепутать их имена с именами функций-членов, к именам переменных-членов
имена переменных-членов дополняются символом _. Украшение имёт переменных-членов - обычная практика в объектно-ориентированном
программировании.
В этом определении Rectangle ширина и высота по-прежнему представлены так, как если бы они были
переменные-члены:

.. code-block:: d

    auto garden = Rectangle(10, 20);
    writefln("width: %s, height: %s",
    garden.width, garden.height);

Если нет функции свойства, изменяющей переменную-член, то этот член
член фактически доступен только для чтения извне:

.. code-block:: d

    garden.width = 100; // ← ошибка компиляции

Это важно для контролируемых модификаций членов. Переменные-члены
могут быть изменены только самим типом Rectangle, чтобы обеспечить согласованность его
объектов.
Если впоследствии окажется, что переменная-член может быть изменена
изменять извне, то просто определите другую функцию свойства для этого члена.

**@property**

Функции свойств также могут быть определены с помощью атрибута @property.
Однако, в качестве лучшей практики, использование этого атрибута не рекомендуется.

.. code-block:: d

    import std.stdio;

    struct Foo
    {
        @property int a() const
        {
            return 4;
        }

        int b() const
        { // ← Defined without @property
            return 42;
        }
    }

    void main()
    {
        auto f = Foo();
        writeln(typeof(f.a).stringof);
        writeln(typeof(f.b).stringof);
    }


Единственный эффект от атрибута @property - это определение типа выражения
выражения, которое синтаксически может быть вызовом функции свойства. Как видно из
ниже, типы выражений f.a и f.b различны:

.. code-block:: console

    int ← The type of the expression f.a (the return type)
    const int() ← The type of the member function Foo.b

